9. 携程¶
本章主要讲解携程问题,从迭代器开始逐渐过渡到携程的概念和用法。
9.1. 迭代器¶
可迭代:可直接被for循环使用的对象叫可迭代的(Iterable), 注意这个是个形容词,用来形容一个对象是否可以迭代。
迭代器:不但可以作用于for循环,还可以被next调用的,叫迭代器(Itrator), 这个是一个名词。
通过定义我们知道,迭代器都是可迭代的,可迭代的未必是迭代器。
我们常见的range
函数就是可迭代的,我们也经常用,还有比如list
对象等。
可以用
isinstance
判断是否可迭代和是否是迭代器,参看下面代码:from collections import Iterable l = [1,2,3,4] isinstance(l, Iterable) from collections import Iterator isinstance((x for x in range(10)), Iterator)
iterable和Iterator的关系,可以通过iter函数运算,简而言之就是把一个对象转换成一个迭代器
isinstance(iter('abc'), Iterator)
for
循环原理先判断对象是否为可迭代对象
不是的话直接报错,抛出TypeError异常
是的话则调用
iter
方法,返回一个迭代器
不断地调用迭代器的next方法,每次按序返回迭代器中的一个值
迭代到最后,没有更多元素了就抛出异常
StopIteration
,这个异常python
自己会处理,不会暴露给开发者看个小代码:
# for循环原理 x = [1, 2, 3] x_iterator = x.__iter__() try: while True: print(next(x_iterator)) #.__next__()) except StopIteration: print("STOP")
9.2. 生成器¶
生成器是特指一种一边循环一边计算的机制的实现(generator),是一种特定的迭代器。
它是一个算法/函数,这个函数可以返回多个返回值,想得到这个函数需要用next
函数调用这个生成器,
每次调用next
函数的时候生成器会计算下一个值,利用yield
返回一个值,这样如果有多个yield
这个
函数就能返回不同的值。
最后一次被调动,没有值返回的时候则抛出StopIteration异常。
生成器可以直接创建,如下面案例:
L = [x * x for x in range(10)] #生成列表
g = (x * x for x in range(10)) #得到一个生成器
第一个L
是用列表生成式创建的列表,而第二个则是创建了一个生成器(generator),而不是一个元祖生成器,因为世上根本
就没有元祖生成器,用的人多了,也没有!
可以粗暴的理解成函数中包含yield叫generator
,生成器可以被next
调用,此时遇到函数中的yield
则返回,
下次被next
调用的时候则继续上次返回的点执行,这样一个函数可以被多次调用,每次调用执行一段代码。
生成器案例1:
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
if __name__ == '__main__':
try:
g1 = odd()
while True:
a = next(g1)
print(a)
except StopIteration:
print("odd DONE")
需要注意生成器的使用方法,我先调用了下生成器,得到一个生成器变量g1
后,此时生成器函数odd()
还没
真正运行,我需要调用next
,每调用一次生成器会运行一段代码,到yield
后就停止,此时返回值就是yield
后面
的值,一直运行下去,直到最后发出StopIteration
异常。
案例1的生成器运行结果如下:
step 1
1
step 2
3
step 3
5
odd DONE
利用生成器解决斐波那契数列问题:
#coding=utf-8
# 利用循环解决斐波那契问题
def fib_loop(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
# 利用生成器
def fib_g(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
if __name__ == '__main__':
print("循环的斐波那契:")
fib_loop(5)
print("生成器的斐波那契:")
g = fib_g(5)
try:
while True:
a = next(g)
print(a)
except StopIteration:
print("STOP")
9.3. 协程¶
还没弄完
- 历史
- 3.4引入协程概念,用yield实现
- 3.5 引入协程语法
- 实现包asyncio,tornado, gevent
- 定义:“协程 是为非抢占式多任务产生子程序的计算机程序组件,
协程允许不同入口点在不同位置暂停或开始执行程序”。
从技术的角度来说,“协程就是你可以暂停执行的函数”。
如果你把它理解成“就像生成器一样”,那么你就想对了。
- 使用:
- yield关键字
- send关键字
- 案例v1
- 协程的四个状态
- inspect.getgeneratorstate(…) 函数确定,该函数会返回下述字符串中的一个:
- GEN_CREATED:等待开始执行
- GEN_RUNNING:解释器正在执行
- GEN_SUSPENED:在yield表达式处暂停
- GEN_CLOSED:执行结束
- next预激(prime)
- 完整执行过程v2
- 协程终止
- 协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的对象)。
- 终止协程的一种方式:发送某个哨符值,让协程退出。内置的 None 和Ellipsis 等常量经常用作哨符值==。
- 异常
- 客户段代码可以在生成器对象上调用两个方法
- generator.throw(Exctpiton):
致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。
- generator.close()
致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。
如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),
调用方不会报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。
- v03
- yield from
- 为了得到返回值,协程必须正常终止;
- 然后生成器对象会抛出StopIteration 异常,异常对象的 value 属性保存着返回的值。from
- yield from 从内部捕获StopIteration异常
- 并且把StopIteration异常value属性子作为yield from表达式的返回值
- v04
- v05
- 委派生成器
- 包含 yield from 表达式的生成器函数
- 委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器。
- 子生成器再把产出的值发给调用方。
- 子生成器返回之后,解释器会抛出 StopIteration 异常,并把返回值附加到异常对象上,
此时委派生成器会恢复。
- v06
# asyncio
- python3.4开始引入的标准库,内置了对移步io的支持
- asyncio本身是一个消息循环,
- 步骤
- 创建消息循环
- 把协程导入
- 关闭
- 案例08
- 案例09-两个tasks
- 案例v10-得到多个网站
# async and await
- 为了更好的表示异步io
- python3.5 开始引入
- 让coroutine代码更简洁
- 使用上,可以简单进行替换
- 可以把 @asyncio.coroutine 替换成async
- yield from 替换成await
- 案例v11, 把案例09直接替换
# aiohttp
- 介绍
- asyncio实现单线程并发IO,在客户端用处不大
- 在服务器端可以asyncio+coroutine配合,因为http是io操作
- asyncio实现了TCP,UIDP,SSL等协议
- aiohttp是给予asyncio实现的HTTP框架
- pip install aiohttp
- 案例07
# concurrent.futures
- python3新增的库
- 类似其他语言的线程池的概念
- 此模块利用multiprocessiong实现真正的平行计算
- 核心原理是:concurrent.futures会以子进程的形式,平行的运行多个python解释器,从而令python程序可以利用多核CPU来提升执行速度。
由于子进程与主解释器相分离,所以他们的全局解释器锁也是相互独立的。每个子进程都能够完整的使用一个CPU内核。
- concurrent.futures.Executor
- ThreadPoolExecutor
- ProcessPoolExecutor
- submit(fn, args, kwargs)
- fn:异步执行的函数
- args,kwargs:参数
# 官方死锁案例
import time
def wait_on_b():
time.sleep(5)
print(b.result()) #b不会完成,他一直在等待a的return结果
return 5
def wait_on_a():
time.sleep(5)
print(a.result()) #同理a也不会完成,他也是在等待b的结果
return 6
executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)
- 案例v14
- map(fn, \*iterables, timeout=None)
- 跟map函数类似
- 函数需要异步执行
- timeout: 超时时间
- 案例 v12
- 案例v15
- 起执行结果是list,数据需要从list中取出来
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
print(list(executor.map(sleeper, x)))
- submit和map根据需要选一个即可
- 案例v13
- Future
- 未来需要完成的任务
- future 实例由Excutor.submit创建
- 案例v17